#include "cli_transfile.h"
#include "command.h"
#include "get_hash.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/mman.h>
#include <fcntl.h>

static int getFileInfo(int file_fd, const char *file_path, const char *dest_path, FileInfo_t *file_info)
{
    struct stat file_stat;
    if (fstat(file_fd, &file_stat) == -1)
    {
        PRINT_ERROR("fstat");
        return -1;
    }

    const char *file_name = strrchr(file_path, '/');
    if (file_name == NULL)
    {
        file_name = file_path;
    }
    else
    {
        file_name++;
    }

    strncpy(file_info->file_name, file_name, MAX_PATH_LEN - 1);
    file_info->file_name[MAX_PATH_LEN - 1] = '\0';
    file_info->file_size = file_stat.st_size;
    file_info->offset = 0;
    file_info->is_complete = false;

    memset(file_info->md5, 0, sizeof(file_info->md5)); // 未来实现计算文件MD5
    getHashMD5(file_path, file_info->md5);

    return 0;
}

#ifdef SHOW_PROGRESS_BAR
static void printProgressBar(long current_size, long total_size, double speed, double remaining_time)
{
    int barWidth = 25; // Width of the progress bar
    float progress = (float)current_size / total_size;
    int pos = barWidth * progress;

    printf("[");
    for (int i = 0; i < barWidth; ++i)
    {
        if (i < pos)
            printf("=");
        else if (i == pos)
            printf(">");
        else
            printf(" ");
    }
    printf("] %d%% | %.2f MB/s | ETA: %.2f s\r", (int)(progress * 100), speed, remaining_time);
    fflush(stdout);
}
#endif

void uploadFile(void *arg)
{
    // command在外面是一个局部变量，这里单独保存信息
    Command_t *command = (Command_t *)arg;

    int socket_fd = command->client_fd; // 这里是tunnel_fd
    char file_path[MAX_PATH_LEN];
    char dest_path[MAX_PATH_LEN];
    strcpy(file_path, command->arg1);
    strcpy(dest_path, command->arg2);

    if (strlen(dest_path) == 0)
    {
        strcpy(dest_path, file_path);
    }

    // PRINT_INFO("uploading file: %s -> %s", file_path, dest_path);

    int fd = open(file_path, O_RDONLY);
    if (fd == -1)
    {
        PRINT_ERROR("failed to open file");
        return;
    }

    FileInfo_t file_info;
    bzero(&file_info, sizeof(FileInfo_t));
    getFileInfo(fd, file_path, dest_path, &file_info);

    // 发送文件信息结构体

    if (send(socket_fd, &file_info, sizeof(FileInfo_t), 0) != sizeof(file_info))
    {
        PRINT_ERROR("failed to send file info");
        close(fd);
        return;
    }

    long bytes_received = recv(socket_fd, &file_info, sizeof(FileInfo_t), 0);
    if (bytes_received != sizeof(FileInfo_t))
    {
        PRINT_ERROR("failed to recv file_info");
        return;
    }
    // 使用 sendfile 发送文件数据
    long offset = file_info.offset;
    long size = file_info.file_size - file_info.offset;
    PRINT_INFO("start at %ld", file_info.offset);
    ssize_t sent_bytes;
    while (size > 0)
    {
        sent_bytes = sendfile(socket_fd, fd, &offset, file_info.file_size);
        size -= sent_bytes;
    }

    if (sent_bytes == -1 || size > 0)
    {
        PRINT_ERROR("failed to send file");
        close(fd);
        return;
    }

    close(fd);
    PRINT_INFO("upload file success");
}

void downloadFile(void *arg)
{
    Command_t *command = (Command_t *)arg;
    int socket_fd = command->client_fd; // 这里是tunnel_fd
    char file_path[MAX_PATH_LEN];
    char save_path[MAX_PATH_LEN];
    strcpy(file_path, command->arg1);
    strcpy(save_path, command->arg2);
    if (strlen(save_path) == 0)
    {
        strcpy(save_path, file_path);
    }

    PRINT_INFO("downloading file: %s -> %s", file_path, save_path);

    ssize_t bytes_received, bytes_written;

    // 接收文件信息结构体
    FileInfo_t file_info;
    memset(&file_info, 0, sizeof(FileInfo_t));
    // 目前只能下载当前一级目录下的文件
    strcpy(file_info.file_name, file_path);

    int file_fd;
    file_fd = open(save_path, O_RDWR | O_CREAT | O_APPEND, 0666);
    if (file_fd == -1)
    {
        PRINT_ERROR("open file failed");
        return;
    }

    struct stat fileStat;
    fstat(file_fd, &fileStat);
    file_info.offset = fileStat.st_size;

    // 发送文件信息结构体
    if (send(socket_fd, &file_info, sizeof(FileInfo_t), 0) != sizeof(FileInfo_t))
    {
        PRINT_ERROR("failed to send file info");
        close(file_fd);
        return;
    }

    bytes_received = recv(socket_fd, &file_info, sizeof(FileInfo_t), 0);
    if (bytes_received != sizeof(FileInfo_t))
    {
        PRINT_ERROR("failed to recv file_info");
        close(file_fd);
        return;
    }
    if (file_info.file_size == 0)
    {
        PRINT_ERROR("download failed!");
        close(file_fd);
        return;
    }
    // 接收文件内容并写入目标文件
    long remaining_bytes = file_info.file_size - file_info.offset;
    long total_bytes = file_info.file_size;
    long downloaded_bytes = total_bytes - remaining_bytes;
    // PRINT_DEBUG("remain size: %ld\n", remaining_bytes);

    char buffer[BUFFER_SIZE];

#ifdef SHOW_PROGRESS_BAR
    time_t start_time = time(NULL);
    time_t current_time;
    double elapsed_time;
    double speed;
    double remaining_time;
#endif

    while (remaining_bytes > 0)
    {
        bytes_received = recv(socket_fd, buffer, BUFFER_SIZE, 0);
        if (bytes_received <= 0)
        {
            PRINT_ERROR("failed to recv file buffer");
            close(file_fd);
            return;
        }

        bytes_written = write(file_fd, buffer, bytes_received);
        if (bytes_written != bytes_received)
        {
            PRINT_ERROR("failed to write file");
            close(file_fd);
            return;
        }
        remaining_bytes -= bytes_written;
        downloaded_bytes += bytes_written;

#ifdef SHOW_PROGRESS_BAR
        // 计算下载速度和剩余时间
        current_time = time(NULL);
        elapsed_time = difftime(current_time, start_time);
        speed = (downloaded_bytes / 1024.0 / 1024.0) / elapsed_time; // MB/s
        remaining_time = (remaining_bytes / 1024.0 / 1024) / speed;  // seconds
        // 更新进度条
        printProgressBar(downloaded_bytes, total_bytes, speed, remaining_time);
#endif
    }

    close(file_fd);

    char md5[33] = {0};
    getHashMD5(save_path, md5);
    if (strcmp(file_info.md5, md5) != 0)
    {
        PRINT_INFO("MD5 diff!");
        PRINT_DEBUG("file_info.md5: %s\nsave_file.md5: %s", file_info.md5, md5);
        return;
    }

    PRINT_INFO("download file success");
}